package jmu.tools.file.sync.util ;


import java.io.File ;
import java.util.ArrayList ;
import java.util.LinkedList ;
import java.util.List ;
import java.util.Map.Entry ;

import jmu.tools.file.sync.FileSyncTask ;


/**
 * Handles multi-threading work
 * 
 * @author vkushnir
 */
public class ThreadManager
{
    public static class WorkingTaskEntry implements Entry < File, File >
    {
        private File key = null ;
        private File value = null ;

        public WorkingTaskEntry ( final File key, final File value )
        {
            if ( null == key )
            {
                throw new IllegalArgumentException ( "key can't be null" ) ;
            }

            this.key = key ;
            this.value = value ;
        }

        @Override
        public boolean equals ( final Object o )
        {
            // compare by reference 
            if ( super.equals ( o ) )
            {
                return true ;
            }

            // filter out by type
            if ( ! (o instanceof WorkingTaskEntry) )
            {
                return false ;
            }

            // objects are equal when their keys are equal
            return hashCode () == o.hashCode () ;
        }

        @Override
        public File getKey ()
        {
            return key ;
        }

        @Override
        public File getValue ()
        {
            return value ;
        }

        @Override
        public int hashCode ()
        {
            return key.hashCode () ;
        }

        @Override
        public File setValue ( final File value )
        {
            return this.value = value ;
        }
    }

    public static class WorkingThread extends Thread
    {
        private ThreadManager manager ;
        private AbstractAction action ;

        public WorkingThread ( ThreadManager manager, AbstractAction action )
        {
            this.manager = manager ;
            this.action = action ;
        }

        public WorkingThread doStart ()
        {
            synchronized ( manager.getLocker () )
            {
                start () ;
            }

            return this ;
        }

        @Override
        public void run ()
        {
            int loop = 0 ;

            while ( !isInterrupted () )
            {
                while ( !manager.isEmpty () )
                {
                    WorkingTaskEntry entry = null ;
                    synchronized ( manager.getLocker () )
                    {
                        if ( !manager.isEmpty () )
                        {
                            entry = manager.get () ;
                        }
                    }
                    if ( null != entry )
                    {
                        action.handle ( manager, entry.getKey (), entry.getValue () ) ;
                        loop = 0 ;
                    }
                }

                // stop work if there is nothing to do
                if ( loop == EMPTY_LOOPS )
                {
                    interrupt () ;
                    continue ;
                }

                // update empty loop counter
                loop++ ;

                // do pause
                sleep () ;
            }
        }

        private void sleep ()
        {
            try
            {
                sleep ( 1000 ) ;
            }
            catch ( InterruptedException e )
            {
                // ignore
            }
        }
    }

    protected static int EMPTY_LOOPS = 5 ;

    private final Object locker = new Object () ;

    private final List < WorkingTaskEntry > tasks = new LinkedList < WorkingTaskEntry > () ;
    private List < WorkingThread > threads = null ;
    private AbstractAction action = null ;
    private int count = FileSyncTask.THREAD_COUNT_DEFAULT ;

    private boolean processed = false ;

    /**
     * Create new instance
     * 
     * @param count
     * @param action
     */
    public ThreadManager ( final int count, final AbstractAction action )
    {
        this.threads = new ArrayList < WorkingThread > ( count ) ;
        this.action = action ;
        this.count = count ;
    }

    /**
     * Add new task
     * 
     * @param source
     * @param destination
     */
    public void add ( final File source, final File destination )
    {
        tasks.add ( new WorkingTaskEntry ( source, destination ) ) ;
    }

    /**
     * Get task from the list
     * 
     * @return task
     */
    public WorkingTaskEntry get ()
    {
        synchronized ( getLocker () )
        {
            if ( !tasks.isEmpty () )
            {
                return tasks.remove ( 0 ) ;
            }
        }
        return null ;
    }

    /**
     * Check if task list is empty
     * 
     * @return true or false
     */
    public boolean isEmpty ()
    {
        synchronized ( getLocker () )
        {
            return tasks.isEmpty () ;
        }
    }

    public void process ()
    {
        // start threads
        start () ;

        // stop threads
        stop () ;
    }

    @Override
    protected void finalize () throws Throwable
    {
        stop () ;
        super.finalize () ;
    }

    protected Object getLocker ()
    {
        return locker ;
    }

    private void start ()
    {
        synchronized ( getLocker () )
        {
            if ( !processed )
            {
                for ( int i = 0 ; i < count ; i++ )
                {
                    threads.add ( new WorkingThread ( this, action ).doStart () ) ;
                }
                processed = true ;
            }
        }
    }

    private void stop ()
    {
        WorkingThread t = null ;
        while ( null != threads && !threads.isEmpty () )
        {
            t = threads.remove ( 0 ) ;
            try
            {
                if ( t.isAlive () && !t.isInterrupted () )
                {
                    t.join () ;
                }
            }
            catch ( InterruptedException e )
            {
                // ignore
            }
        }

        threads.clear () ;
    }
}
